﻿using System;
using System.Collections.Generic;
using System.Reflection;
using MugenInjection.Delegates;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;

// ReSharper disable CheckNamespace

namespace MugenInjection.Core.Components
// ReSharper restore CheckNamespace
{
    /// <summary>
    ///     Represents the class that can convert to injector wrapper.
    /// </summary>
    public class InjectorWrapperConverter : ConverterBase
    {
        #region Fields

        /// <summary>
        ///     Gets the default constructor types for a managed wrappers.
        /// </summary>
        public static readonly Type[] ManagedWrapperConstructorTypes = new[]
                                                                           {
                                                                               typeof (IInjector),
                                                                               typeof (IBindingContext)
                                                                           };

        private readonly NotifyCollectionImpl<Type> _injectorWrapperTypes;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="InjectorWrapperConverter" /> class.
        /// </summary>
        public InjectorWrapperConverter()
            : base(DefaultPriority, ServiceType.Simple | ServiceType.Activatable)
        {
            _injectorWrapperTypes = new NotifyCollectionImpl<Type> { typeof(Managed<>), typeof(UoW<>) };
            _injectorWrapperTypes.Added += InjectorWrapperTypesOnAdded;
            _injectorWrapperTypes.Changed += InjectorWrapperTypesOnChanged;
            _injectorWrapperTypes.Removed += InjectorWrapperTypesOnRemoved;
        }

        #endregion

        #region Properties

        /// <summary>
        ///     Gets the collection of supported managed types.
        /// </summary>
        public IList<Type> InjectorWrapperTypes
        {
            get { return _injectorWrapperTypes; }
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Occurs when managed type was added.
        /// </summary>
        protected virtual void InjectorWrapperTypesOnAdded(Type type)
        {
            Validate.ArgumentNotNull(type, "type");
            if (type.GetConstructor(ManagedWrapperConstructorTypes) == null)
                throw new ArgumentException(
                    String.Format("The managed type should has constructor with types {0}",
                                  InjectorUtilsInternal.FormatTypes(ManagedWrapperConstructorTypes)), "type");
            OnSupportedTypeChanged(TypeChangedAction.Add, type);
        }

        /// <summary>
        ///     Occurs when managed type was changed.
        /// </summary>
        protected virtual void InjectorWrapperTypesOnChanged(Type oldValue, Type newValue)
        {
            OnSupportedTypeChanged(TypeChangedAction.Remove, oldValue);
            InjectorWrapperTypesOnAdded(newValue);
        }

        /// <summary>
        ///     Occurs when managed type was removed.
        /// </summary>
        protected virtual void InjectorWrapperTypesOnRemoved(Type type)
        {
            OnSupportedTypeChanged(TypeChangedAction.Remove, type);
        }

        /// <summary>
        ///     Converts the specified object to injector wrapper.
        /// </summary>
        /// <param name="inputValue">
        ///     The specified object to convert, by default is always <see cref="IBindingContext" />.
        /// </param>
        /// <param name="originalType">The specified type of object.</param>
        /// <param name="createManagedWrapper">The specified delegate to create managed-wrapper.</param>
        /// <returns>An instance of managed-wrapper.</returns>
        protected virtual object ConvertToInjectorWrapper(object inputValue, Type originalType,
                                                         ActivatorDelegate createManagedWrapper)
        {
            IBindingContext context = InjectorUtilsInternal.GetConverterContext(inputValue).BindingContext;
            context.Service = originalType;
            return createManagedWrapper(context.CallInjector, context);
        }

        #endregion

        #region Overrides of ConverterBase

        /// <summary>
        ///     Gets the supported types.
        /// </summary>
        public override IEnumerable<Type> SupportedTypes
        {
            get { return _injectorWrapperTypes; }
        }

        /// <summary>
        ///     Tries to convert instance.
        /// </summary>
        /// <param name="bindingContext">The specified type to convert from.</param>
        /// <param name="originalType">The type to converted to.</param>
        /// <param name="convertAction">An delegate to convert instance.</param>
        /// <returns>
        ///     If <c>true</c> can be converted; otherwise <c>false</c>.
        /// </returns>
        protected override ConverterResult TryConvertInternal(IBindingContext bindingContext, out Type originalType, out Func<object, object> convertAction)
        {
            var service = bindingContext.Service;
            originalType = null;
            convertAction = null;

#if !NETFX_CORE
            if (!service.IsGenericType)
#else
            if (!service.GetTypeInfo().IsGenericType)
#endif
                return ConverterResult.NotSupported;

            Type[] genericArguments = service.GetGenericArguments();
            if (genericArguments.Length != 1)
                return ConverterResult.NotSupported;

            bool isManaged = false;
            for (int index = 0; index < _injectorWrapperTypes.Count; index++)
            {
                Type managedType = _injectorWrapperTypes[index];
                if (!InjectorUtilsInternal.IsEqualGenericDefinition(service, managedType)) continue;
                isManaged = true;
                break;
            }
            if (!isManaged)
                return ConverterResult.NotSupported;

            ConstructorInfo constructor = service.GetConstructor(ManagedWrapperConstructorTypes);
            if (constructor == null)
                return ConverterResult.NotSupported;

            originalType = genericArguments[0];
            ActivatorDelegate activatorDelegate =
                InjectorUtils.ReflectionAccessProvider.GetActivatorDelegate(constructor);
            Type origType = originalType;
            convertAction = o => ConvertToInjectorWrapper(o, origType, activatorDelegate);
            return ConverterResult.SupportedWithCache;
        }

        #endregion
    }
}